package redis

import (
	"context"

	"git.blindage.org/21h/redis-operator/pkg/controller/manifests"
	v1 "k8s.io/api/apps/v1"
	"k8s.io/api/policy/v1beta1"

	blindagev1alpha1 "git.blindage.org/21h/redis-operator/pkg/apis/blindage/v1alpha1"
	raven "github.com/getsentry/raven-go"
	"github.com/go-logr/logr"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

// ReconcilePodDisruptionBudget means full reconcile of PodDisruptionBudget
func (r *ReconcileRedis) ReconcilePodDisruptionBudget(reqLogger logr.Logger, cr *blindagev1alpha1.Redis, pdbName string, pdbSpec v1beta1.PodDisruptionBudgetSpec) (reconcile.Result, error) {

	newPodDisruptionBudget := manifests.GeneratePdbResource(cr, pdbName, pdbSpec)

	if err := controllerutil.SetControllerReference(cr, &newPodDisruptionBudget, r.scheme); err != nil {
		raven.CaptureErrorAndWait(err, nil)
		return reconcile.Result{}, err
	}

	foundPodDisruptionBudget := v1beta1.PodDisruptionBudget{}
	err := r.client.Get(context.TODO(), types.NamespacedName{Name: newPodDisruptionBudget.Name, Namespace: newPodDisruptionBudget.Namespace}, &foundPodDisruptionBudget)
	if err != nil && errors.IsNotFound(err) {
		reqLogger.Info("Creating a PodDisruptionBudget", "Namespace", newPodDisruptionBudget.Namespace, "Name", newPodDisruptionBudget.Name)
		err = r.client.Create(context.TODO(), &newPodDisruptionBudget)
		if err != nil {
			reqLogger.Info("Creating PodDisruptionBudget error", "Namespace", newPodDisruptionBudget.Namespace, "Name", newPodDisruptionBudget.Name, "Error", err)
			raven.CaptureErrorAndWait(err, nil)
			return reconcile.Result{}, err
		}
	} else if err != nil {
		raven.CaptureErrorAndWait(err, nil)
		return reconcile.Result{}, err
	} else {
		if reconcileRequired, reconResource := reconcilePodDisruptionBudgetApply(foundPodDisruptionBudget, newPodDisruptionBudget); reconcileRequired {
			reqLogger.Info("Updating PodDisruptionBudget", "Namespace", reconResource.Namespace, "Name", reconResource.Name)
			if err = r.client.Update(context.TODO(), &reconResource); err != nil {
				reqLogger.Info("Reconcile PodDisruptionBudget error", "Namespace", foundPodDisruptionBudget.Namespace, "Name", foundPodDisruptionBudget.Name, "Error", err)
				raven.CaptureErrorAndWait(err, nil)
				return reconcile.Result{}, err
			}
		}
	}

	reqLogger.Info("Reconcile PodDisruptionBudget complete", "PodDisruptionBudget Name", pdbName, "Namespace", cr.Namespace, "Name", cr.Name)
	return reconcile.Result{}, nil
}

// ReconcileConfigmap means full reconcile of configmap
func (r *ReconcileRedis) ReconcileConfigmap(reqLogger logr.Logger, cr *blindagev1alpha1.Redis, configmapName string, configmapData map[string]string) (reconcile.Result, error) {

	newConfigmap := manifests.GenerateConfigmap(cr, configmapName, configmapData)

	if err := controllerutil.SetControllerReference(cr, &newConfigmap, r.scheme); err != nil {
		raven.CaptureErrorAndWait(err, nil)
		return reconcile.Result{}, err
	}

	foundConfigmap := corev1.ConfigMap{}
	err := r.client.Get(context.TODO(), types.NamespacedName{Name: newConfigmap.Name, Namespace: newConfigmap.Namespace}, &foundConfigmap)
	if err != nil && errors.IsNotFound(err) {
		reqLogger.Info("Creating a Sentinel Configmap", "Namespace", newConfigmap.Namespace, "Name", newConfigmap.Name)
		err = r.client.Create(context.TODO(), &newConfigmap)
		if err != nil {
			reqLogger.Info("Creating Sentinel Configmap error", "Namespace", newConfigmap.Namespace, "Name", newConfigmap.Name, "Error", err)
			raven.CaptureErrorAndWait(err, nil)
			return reconcile.Result{}, err
		}
	} else if err != nil {
		raven.CaptureErrorAndWait(err, nil)
		return reconcile.Result{}, err
	} else {
		if reconcileRequired, reconResource := reconcileConfigmapApply(foundConfigmap, newConfigmap); reconcileRequired {
			reqLogger.Info("Updating Sentinel Configmap", "Namespace", reconResource.Namespace, "Name", reconResource.Name)
			if err = r.client.Update(context.TODO(), &reconResource); err != nil {
				reqLogger.Info("Reconcile Sentinel Configmap error", "Namespace", foundConfigmap.Namespace, "Name", foundConfigmap.Name, "Error", err)
				raven.CaptureErrorAndWait(err, nil)
				return reconcile.Result{}, err
			}
		}
	}

	reqLogger.Info("Reconcile configmap complete", "Configmap Name", configmapName, "Namespace", cr.Namespace, "Name", cr.Name)
	return reconcile.Result{}, nil
}

// ReconcileService one function to reconcile all services
func (r *ReconcileRedis) ReconcileService(reqLogger logr.Logger, cr *blindagev1alpha1.Redis, serviceName string, ports map[string]int32, selector map[string]string) (reconcile.Result, error) {

	// reconcile Service
	newService := manifests.GenerateService(cr, serviceName, ports, selector)

	if err := controllerutil.SetControllerReference(cr, &newService, r.scheme); err != nil {
		raven.CaptureErrorAndWait(err, nil)
		return reconcile.Result{}, err
	}

	foundService := corev1.Service{}
	err := r.client.Get(context.TODO(), types.NamespacedName{Name: newService.Name, Namespace: newService.Namespace}, &foundService)
	if err != nil && errors.IsNotFound(err) {
		reqLogger.Info("Creating a new Service", "Namespace", newService.Namespace, "Name", newService.Name)
		err = r.client.Create(context.TODO(), &newService)
		if err != nil {
			reqLogger.Info("Creating Service error", "Namespace", newService.Namespace, "Name", newService.Name, "Error", err)
			raven.CaptureErrorAndWait(err, nil)
			return reconcile.Result{}, err
		}
	} else if err != nil {
		raven.CaptureErrorAndWait(err, nil)
		return reconcile.Result{}, err
	} else {
		if reconcileRequired, reconResource := reconcileServiceApply(foundService, newService); reconcileRequired {
			reqLogger.Info("Updating Service", "Namespace", reconResource.Namespace, "Name", reconResource.Name)
			if err = r.client.Update(context.TODO(), &reconResource); err != nil {
				reqLogger.Info("Reconcile Service error", "Namespace", foundService.Namespace, "Name", foundService.Name, "Error", err)
				raven.CaptureErrorAndWait(err, nil)
				return reconcile.Result{}, err
			}
		}
	}
	reqLogger.Info("Reconcile service complete", "Service Name", serviceName, "Namespace", cr.Namespace, "Name", cr.Name)
	return reconcile.Result{}, nil
}

// ReconcileDeployment one function to reconcile all deployments
func (r *ReconcileRedis) ReconcileDeployment(reqLogger logr.Logger, cr *blindagev1alpha1.Redis, deployment v1.Deployment) (reconcile.Result, error) {

	if err := controllerutil.SetControllerReference(cr, &deployment, r.scheme); err != nil {
		raven.CaptureErrorAndWait(err, nil)
		return reconcile.Result{}, err
	}

	// controller deployment
	foundDeployment := v1.Deployment{}
	err := r.client.Get(context.TODO(), types.NamespacedName{Name: deployment.Name, Namespace: deployment.Namespace}, &foundDeployment)
	if err != nil && errors.IsNotFound(err) {
		reqLogger.Info("Creating Deployment", "Namespace", deployment.Namespace, "Name", deployment.Name)
		err = r.client.Create(context.TODO(), &deployment)
		if err != nil {
			reqLogger.Info("Creating Deployment error", "Namespace", deployment.Namespace, "Name", deployment.Name, "Error", err)
			raven.CaptureErrorAndWait(err, nil)
			return reconcile.Result{}, err
		}
	} else if err != nil {
		raven.CaptureErrorAndWait(err, nil)
		return reconcile.Result{}, err
	} else {
		if reconcileRequired, reconResource := reconcileDeploymentApply(foundDeployment, deployment); reconcileRequired {
			reqLogger.Info("Updating Deployment", "Namespace", reconResource.Namespace, "Name", reconResource.Name)
			if err = r.client.Update(context.TODO(), &reconResource); err != nil {
				reqLogger.Info("Reconcile Deployment error", "Namespace", foundDeployment.Namespace, "Name", foundDeployment.Name, "Error", err)
				raven.CaptureErrorAndWait(err, nil)
				return reconcile.Result{}, err
			}
		}
	}
	reqLogger.Info("Reconcile Deployment complete", "Deployment Name", deployment.Name, "Namespace", cr.Namespace, "Name", cr.Name)
	return reconcile.Result{}, nil
}

// ReconcileStatefulSet one function to reconcile all StatefulSets
func (r *ReconcileRedis) ReconcileStatefulSet(reqLogger logr.Logger, cr *blindagev1alpha1.Redis, newStatefulset v1.StatefulSet) (reconcile.Result, error) {

	if err := controllerutil.SetControllerReference(cr, &newStatefulset, r.scheme); err != nil {
		raven.CaptureErrorAndWait(err, nil)
		return reconcile.Result{}, err
	}

	// controller statefulset
	foundStatefulset := v1.StatefulSet{}
	err := r.client.Get(context.TODO(), types.NamespacedName{Name: newStatefulset.Name, Namespace: newStatefulset.Namespace}, &foundStatefulset)
	if err != nil && errors.IsNotFound(err) {
		reqLogger.Info("Creating a new Redis Statefulset", "Namespace", newStatefulset.Namespace, "Name", newStatefulset.Name)
		err = r.client.Create(context.TODO(), &newStatefulset)
		if err != nil {
			reqLogger.Info("Creating Redis Statefulset error", "Namespace", newStatefulset.Namespace, "Name", newStatefulset.Name, "Error", err)
			raven.CaptureErrorAndWait(err, nil)
			return reconcile.Result{}, err
		}
	} else if err != nil {
		raven.CaptureErrorAndWait(err, nil)
		return reconcile.Result{}, err
	} else {
		if reconcileRequired, reconResource := reconcileStatefulsetApply(foundStatefulset, newStatefulset); reconcileRequired {
			reqLogger.Info("Updating Redis Statefulset", "Namespace", reconResource.Namespace, "Name", reconResource.Name)
			if err = r.client.Update(context.TODO(), &reconResource); err != nil {
				reqLogger.Info("Reconcile Redis Statefulset error", "Namespace", foundStatefulset.Namespace, "Name", foundStatefulset.Name, "Error", err)
				raven.CaptureErrorAndWait(err, nil)
				return reconcile.Result{}, err
			}
		}
	}

	reqLogger.Info("Reconcile StatefulSet complete", "StatefulSet Name", newStatefulset.Name, "Namespace", cr.Namespace, "Name", cr.Name)
	return reconcile.Result{}, nil
}
